//////////////////////////////////////////////////////////////////////////
// Author:		Dan Polak
// Date:		3/9/2010
// Last Mod:	N/A
//
// File:		Math.h
//
// Purpose:		Define Math functions as well as the vec3 which will be
//				needed for 3D math positions and equations.
//////////////////////////////////////////////////////////////////////////

#ifndef _MATH_H
#define _MATH_H

//STL math file
#include <math.h>

struct vec3f
{
	float x;
	float y;
	float z;
	
	//Constructor
	vec3f(float fx = 0.0f, float fy = 0.0f, float fz = 0.0f)
	{
		x = fx; y = fy; z = fz;
	}

	void SetValues(float fx = 0.0f, float fy = 0.0f, float fz = 0.0f)
	{
		x = fx; y = fy; z = fz;
	}
	
	void scale(float f)
	{
		this->x *= f;
		this->y *= f;
		this->z *= f;
	}

	void make_zero()
	{
		this->x = 0;
		this->y = 0;
		this->z = 0;
	}

	friend float magnitude(vec3f* v)
	{
		float result;
		result = (float)sqrt(v->x*v->x + v->y*v->y + v->z*v->z);
		return result;
  	}

	void normalize()
	{
		float invMag = 1 / magnitude(this);
		this->x *= invMag;
		this->y *= invMag;
		this->z *= invMag;
	}
	
	friend vec3f cross_product(vec3f A, vec3f B)
	{
		vec3f result;
		result.x = A.y*B.z - B.y*A.z;
		result.y = A.z*B.x - B.z*A.x;
		result.z = A.x*B.y - B.x*A.y;

		return result;
	}

	friend float dot_product(vec3f A, vec3f B)
	{
		return (A.x*B.x + A.y*B.y + A.z*B.z);
	}

	//Operators
	vec3f vec3f::operator-(const vec3f &rhs1)
	{
		vec3f temp;
		temp.x = x - rhs1.x;
		temp.y = y - rhs1.y;
		temp.z = z - rhs1.z;
		
		return temp;
	}
	
	vec3f vec3f::operator+(const vec3f &rhs1)
	{
		vec3f temp;
		temp.x = x + rhs1.x;
		temp.y = y + rhs1.y;
		temp.z = z + rhs1.z;
		
		return temp;
	}
};

struct matrix4f
{
	union
	{
		float m[4][4];
		float ma[16];

		struct
		{
			vec3f axis_x; float pad_xw;
			vec3f axis_y; float pad_y;
			vec3f axis_z; float pad_zw;
			vec3f axis_pos; float pad_posw;
		};

		struct
		{
			float xx, xy, xz, xw;
			float yx, yy, yz, yw;
			float zx, zy, zz, zw;
			float wx, wy, wz, ww;	
		};
	};

	matrix4f(float mxx, float mxy, float mxz, float mxw,
			 float myx, float myy, float myz, float myw,
			 float mzx, float mzy, float mzz, float mzw,
			 float mwx, float mwy, float mwz, float mww)
	{
		this->xx = mxx; this->xy = mxy; this->xz = mxz; this->xw = mxw;
		this->yx = myx; this->yy = myy; this->yz = myz; this->yw = myw;
		this->zx = mzx; this->zy = mzy; this->zz = mzz; this->zw = mzw;
		this->wx = mwx; this->wy = mwy; this->wz = mwz; this->ww = mww;
	}

	matrix4f()	{}

	void matrix4f::operator*=(const matrix4f &rhs)
	{
		matrix4f temp(xx * rhs.xx + yx * rhs.xy + zx * rhs.xz + wx * rhs.xw,
					 xy * rhs.xx + yy * rhs.xy + zy * rhs.xz + wy * rhs.xw,
					 xz * rhs.xx + yz * rhs.xy + zz * rhs.xz + wz * rhs.xw,
					 xw * rhs.xx + yw * rhs.xy + zw * rhs.xz + ww * rhs.xw,

					 xx * rhs.yx + yx * rhs.yy + zx * rhs.yz + wx * rhs.yw,
					 xy * rhs.yx + yy * rhs.yy + zy * rhs.yz + wy * rhs.yw,
					 xz * rhs.yx + yz * rhs.yy + zz * rhs.yz + wz * rhs.yw,
					 xw * rhs.yx + yw * rhs.yy + zw * rhs.yz + ww * rhs.yw,

					 xx * rhs.zx + yx * rhs.zy + zx * rhs.zz + wx * rhs.zw,
					 xy * rhs.zx + yy * rhs.zy + zy * rhs.zz + wy * rhs.zw,
					 xz * rhs.zx + yz * rhs.zy + zz * rhs.zz + wz * rhs.zw,
					 xw * rhs.zx + yw * rhs.zy + zw * rhs.zz + ww * rhs.zw,

					 xx * rhs.wx + yx * rhs.wy + zx * rhs.wz + wx * rhs.ww,
					 xy * rhs.wx + yy * rhs.wy + zy * rhs.wz + wy * rhs.ww,
					 xz * rhs.wx + yz * rhs.wy + zz * rhs.wz + wz * rhs.ww,
					 xw * rhs.wx + yw * rhs.wy + zw * rhs.wz + ww * rhs.ww);
		
		for(int i = 0; i < 16; ++i)
		{
			this->ma[i] = temp.ma[i];
		}
	}

	void make_identity()
	{
		for(int i = 0; i < 16; ++i)
		{
			this->ma[i] = 0.0f;
		}

		this->axis_x.x = this->axis_y.y = this->axis_z.z = this->pad_posw = 1.0f;
	}

	friend matrix4f OrthonormalInverse(const matrix4f A)
	{
		matrix4f result;
		result.make_identity();

		result.xx = A.xx;
		result.yx = A.xy;
		result.zx = A.xz;

		result.xy = A.yx;
		result.yy = A.yy;
		result.zy = A.yz;

		result.xz = A.zx;
		result.yz = A.zy;
		result.zz = A.zz;

		result.wx = -dot_product(A.axis_x, A.axis_pos);
		result.wy = -dot_product(A.axis_y, A.axis_pos);
		result.wz = -dot_product(A.axis_z, A.axis_pos);

		return result;
	}

	void make_x_rotation(float f)
	{
		float sin_angle, cos_angle;
		sin_angle = sin(f);
		cos_angle = cos(f);

		this->xx = 1;	this->xy = 0;			this->xz = 0;			this->xw = 0;
		this->yx = 0;	this->yy = cos_angle;	this->yz = sin_angle;	this->yw = 0;
		this->zx = 0;	this->zy = -sin_angle;	this->zz = cos_angle;	this->zw = 0;
		this->wx = 0;	this->wy = 0;			this->wz = 0;			this->ww = 1;
	}

	void make_y_rotation(float f)
	{
		float sin_angle, cos_angle;
		sin_angle = sin(f);
		cos_angle = cos(f);

		this->xx = cos_angle;	this->xy = 0;	this->xz = -sin_angle;	this->xw = 0;
		this->yx = 0;			this->yy = 1;	this->yz = 0;			this->yw = 0;
		this->zx = sin_angle;	this->zy = 0;	this->zz = cos_angle;	this->zw = 0;
		this->wx = 0;			this->wy = 0;	this->wz = 0;			this->ww = 1;
	}

	void make_z_rotation(float f)
	{
		float sin_angle, cos_angle;
		sin_angle = sin(f);
		cos_angle = cos(f);

		this->xx = cos_angle;	this->xy = sin_angle;	this->xz = 0;	this->xw = 0;
		this->yx = -sin_angle;	this->yy = cos_angle;	this->yz = 0;	this->yw = 0;
		this->zx = 0;			this->zy = 0;			this->zz = 1;	this->zw = 0;
		this->wx = 0;			this->wy = 0;			this->wz = 0;	this->ww = 1;
	}

	void rotate_x(float f)
	{
		matrix4f temp;
		temp.make_x_rotation(f);
		*this *= temp;
	}

	void rotate_y(float f)
	{
		matrix4f temp;
		temp.make_y_rotation(f);	

		*this *= temp;
	}

	void rotate_z(float f)
	{
		matrix4f temp;
		temp.make_z_rotation(f);

		*this *= temp;
	}
};

struct Plane
{
	vec3f m_vNormal;
	float m_fdot;

	void Create(vec3f vP, vec3f vNorm)
	{
		vNorm.normalize();	//Saftey Measure
		m_vNormal = vNorm;
		m_fdot = dot_product(vNorm, vP);
	}
};

struct Ray
{
	vec3f m_vStart;
	vec3f m_vDirection;

	void Create(vec3f start, vec3f point)
	{
		m_vStart = start;
		m_vDirection = point - start;
		m_vDirection.normalize();
	}

	//Must be careful to use a vector that is already
	// Normalized for this Create
	void Create_Unsecure(vec3f start, vec3f dir)
	{
		m_vStart = start;
		m_vDirection = dir;
	}
};

struct OBJBase
{
	int m_nType;
};

struct AABB : public OBJBase
{
	vec3f	m_vMinPos;
	vec3f	m_vMaxPos;

	int m_nZone;		//Determines which Zone the object is in.
						//if the zone is -1 it will be computed no matter where it is

	bool m_bChecked;	//Collision variable

	void Create(vec3f vMin, vec3f vMax, int nZone, int nType)
	{
		this->m_nType = nType;
		this->m_nZone = nZone;
		this->m_vMinPos = vMin;
		this->m_vMaxPos = vMax;
		this->m_bChecked = false;
	}

	void OBJTranslate(vec3f vTrans)
	{
		this->m_vMinPos.x += vTrans.x;
		this->m_vMaxPos.x += vTrans.x;

		this->m_vMinPos.y += vTrans.y;
		this->m_vMaxPos.y += vTrans.y;

		this->m_vMinPos.z += vTrans.z;
		this->m_vMaxPos.z += vTrans.z;
	}

	void Render()
	{
		//glBegin(GL_QUADS);
		//{
		//	//sides
		//	glNormal3f(-1.0f, 0.0f, 0.0f);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMinPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMaxPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMaxPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMinPos.y, this->m_vMaxPos.z);

		//	glNormal3f(0.0f, 0.0f, -1.0f);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMinPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMinPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMaxPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMaxPos.y, this->m_vMinPos.z);

		//	glNormal3f(1.0f, 0.0f, 0.0f);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMinPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMaxPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMaxPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMinPos.y, this->m_vMinPos.z);

		//	glNormal3f(0.0f, 0.0f, 1.0f);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMinPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMinPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMaxPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMaxPos.y, this->m_vMaxPos.z);

		//	//Bottom
		//	glNormal3f(0.0f, -1.0f, 0.0f);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMinPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMinPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMinPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMinPos.y, this->m_vMinPos.z);

		//	//Top
		//	glNormal3f(0.0f, 1.0f, 0.0f);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMaxPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMaxPos.y, this->m_vMinPos.z);
		//	glVertex3f(this->m_vMaxPos.x, this->m_vMaxPos.y, this->m_vMaxPos.z);
		//	glVertex3f(this->m_vMinPos.x, this->m_vMaxPos.y, this->m_vMaxPos.z);
		//}
		//glEnd();
	}
};

struct Sphere : public OBJBase
{
	vec3f m_vCenter;
	float m_fRadius;
	//GLUquadricObj* m_pSphere;

	void Create(vec3f vCenter, float fRadius)
	{
		this->m_fRadius = fRadius;
		this->m_vCenter = vCenter;
		//this->m_pSphere = gluNewQuadric();
	}

	void Render()
	{
		//gluSphere(this->m_pSphere, this->m_fRadius, 32, 32);
	}

	void Delete()
	{
		//gluDeleteQuadric(m_pSphere);
	}
};

struct Cylinder : public OBJBase
{	
	vec3f m_vBaseCenter;
	float m_fRadius;
	float m_fHeight;
	int m_nSlices;
	int m_nStacks;
	//GLUquadricObj* m_pCylinder;

	void Create(vec3f vBase, float fHeight, float fRadius, int nSlice, int nStack)
	{
		this->m_vBaseCenter.make_zero();
		this->m_nSlices = nSlice;
		this->m_nStacks = nStack;
		this->m_fHeight = fHeight;
		this->m_fRadius = fRadius;
		this->m_vBaseCenter = vBase;
		//this->m_pCylinder = gluNewQuadric();
		//this->m_nType = MOBJ_CYLINDER;
	}

	void Render()
	{
		//gluCylinder(this->m_pCylinder, this->m_fRadius, this->m_fRadius, this->m_fHeight, this->m_nSlices, this->m_nStacks);
	}
	
	void Delete()
	{
		//gluDeleteQuadric(m_pCylinder);
	}
};

#endif